﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.IO.Packaging;

namespace OPCDiffCore.Relationship
{
    internal class RelationshipAnalyzer : PartAnalyzer
    {
        internal override PartDiff Analyze(PackagePart relationshipOrig, PackagePart relationshipDiff, AnalyzeLevel level)
        {
            PartDiff retPartDif = new PartDiff(relationshipOrig.Uri,
                "application/vnd.openxmlformats-package.relationships+xml");

            // Store both part relationships
            List<PackageRelationship> collOrig = new List<PackageRelationship>(GetOriginalPartRelationshipFromRelationshipPart(relationshipOrig));
            List<PackageRelationship> collDiff = new List<PackageRelationship>(GetOriginalPartRelationshipFromRelationshipPart(relationshipDiff));

            foreach (PackageRelationship relOrig in GetOriginalPartRelationshipFromRelationshipPart(relationshipOrig))
            {
                foreach (PackageRelationship relDiff in GetOriginalPartRelationshipFromRelationshipPart(relationshipDiff))
                {
                    if (relOrig.Id == relDiff.Id &&
                        relOrig.RelationshipType == relDiff.RelationshipType &&
                        relOrig.TargetMode == relDiff.TargetMode &&
                        relOrig.TargetUri == relDiff.TargetUri)
                    {
                        // If relationships are equal, remove them from the collection
                        collOrig.Remove(relOrig);
                        collDiff.Remove(relDiff);
                        break;
                    }
                }
            }

            List<PackageRelationship> collOrigRemove = new List<PackageRelationship>(collOrig);
            List<PackageRelationship> collDiffNew = new List<PackageRelationship>(collDiff);

            // Treat all non equivalent relationship for modifications
            foreach (PackageRelationship relOrig in collOrig)
            {
                foreach (PackageRelationship relDiff in collDiff)
                {
                    if (relOrig.Id == relDiff.Id &&
                        (relOrig.RelationshipType != relDiff.RelationshipType ||
                        relOrig.TargetMode != relDiff.TargetMode ||
                        relOrig.TargetUri != relDiff.TargetUri))
                    {
                        RelationshipChange relChange = BuildRelationshipChange(relOrig, relDiff);
                        retPartDif.Add(
                            new DiffElement(DiffType.Modified, DiffElementType.Relationship) { 
                                Extra = relChange
                            });
                        collOrigRemove.Remove(relOrig);
                        collDiffNew.Remove(relDiff);
                        break;
                    }
                }
            }

            // Treat all relationship remaining in the original part relationships as removed
            foreach (PackageRelationship relOrig in collOrigRemove)
            {
                RelationshipChange relChange = new RelationshipChange(relOrig.Id);
                retPartDif.Add(
                    new DiffElement(DiffType.Removed, DiffElementType.Relationship) { 
                        Extra = relChange ,
                        Description = String.Format("Relationship with Id '{0}' has been removed.", relOrig.Id)
                    });
            }

            // Treat all relationship remaining in the diff part relationships as added
            foreach (PackageRelationship relDiff in collDiffNew)
            {
                RelationshipChange relChange = new RelationshipChange(relDiff.Id);
                retPartDif.Add(
                    new DiffElement(DiffType.Added, DiffElementType.Relationship) { 
                        Extra = relChange ,
                        Description = String.Format("Relationship with: Id->'{0}' - Type->'{1}' - " +
                            " TargetUri->'{2}' - TargetMode'{3}' has been added.", 
                            relDiff.Id,
                            relDiff.RelationshipType,
                            relDiff.TargetUri.ToString(),
                            relDiff.TargetMode)
                    });
            }

            return retPartDif;
        }

        private RelationshipChange BuildRelationshipChange(PackageRelationship orig, PackageRelationship diff)
        {
            RelationshipChange retRelationshipChange = new RelationshipChange(orig.Id) { DiffType = DiffType.Modified };

            // Relationship type
            if (orig.RelationshipType != diff.RelationshipType)
            {
                InternalRelationshipChange intRelChange = new InternalRelationshipChange
                {
                    OldValue = orig.RelationshipType,
                    NewValue = diff.RelationshipType,
                    ChangedElement = InternalRelationshipChangeElement.Type
                };
                retRelationshipChange.ChangeList.Add(intRelChange);
            }

            // Target mode
            if (orig.TargetMode != diff.TargetMode)
            {
                InternalRelationshipChange intRelChange = new InternalRelationshipChange
                {
                    OldValue = orig.TargetMode.ToString(),
                    NewValue = diff.TargetMode.ToString(),
                    ChangedElement = InternalRelationshipChangeElement.TargetMode
                };
                retRelationshipChange.ChangeList.Add(intRelChange);
            }

            // Target URI
            if (orig.TargetUri != diff.TargetUri)
            {
                InternalRelationshipChange intRelChange = new InternalRelationshipChange
                {
                    OldValue = orig.TargetUri.ToString(),
                    NewValue = diff.TargetUri.ToString(),
                    ChangedElement = InternalRelationshipChangeElement.TargetUri
                };
                retRelationshipChange.ChangeList.Add(intRelChange);
            }

            return retRelationshipChange.ChangeList.Count > 0 ? retRelationshipChange : null;
        }

        private PackageRelationshipCollection GetOriginalPartRelationshipFromRelationshipPart(PackagePart relPart)
        {
            if (relPart.Uri.ToString() == "/_rels/.rels")
                return relPart.Package.GetRelationships();
            else
            {
                Uri uriPart = PackUriHelper.GetSourcePartUriFromRelationshipPartUri(relPart.Uri);
                return relPart.Package.GetPart(uriPart).GetRelationships();
            }
        }
    }
}
